package com.nettyPractice;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;


import java.util.logging.Logger;

public class NettyClient {
    private static final Logger logger = Logger.getLogger(NettyClient.class.getName());
    private final String host;
    private final int port;
    private static final Bootstrap bootstrap;

    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    /**
     * 静态代码块（Static Block）是一种特殊的代码块
     * 用于初始化类的静态变量或执行与类加载相关的操作。
     * 静态代码块在类加载时自动执行，并且只执行一次。
     */
    //初始化相关资源
    static {
        //NioEventLoopGroup 是 Netty 中基于 Java NIO 的事件循环组，负责处理 I/O 操作和事件。
        // 这里创建了一个 NioEventLoopGroup 实例，用于管理客户端的 I/O 线程。
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        //Bootstrap 是 Netty 客户端启动辅助类，用于配置和启动客户端。通过 Bootstrap 可以设置客户端的各种参数，如线程组、通道类型等。
        bootstrap = new Bootstrap();
        KryoSerializer kryoSerializer = new KryoSerializer();
        //将之前创建的 NioEventLoopGroup 分配给 Bootstrap，用于处理客户端的 I/O 操作。
        bootstrap.group(eventLoopGroup)
                // 指定客户端使用的通道类型为 NioSocketChannel，这是基于 Java NIO 的客户端套接字通道。
                .channel(NioSocketChannel.class)
                // 添加一个日志处理器，用于记录客户端的 I/O 操作日志，日志级别为 INFO。
                .handler(new LoggingHandler(LogLevel.INFO))
                // 连接的超时时间，超过这个时间还是建立不上的话则代表连接失败
                // 如果15s之内没有发送数据给客户端，就发一次心跳请求
               .option(io.netty.channel.ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                // 添加一个通道初始化器，用于在通道创建时对通道的 ChannelPipeline 进行初始化。
               .handler(new ChannelInitializer<SocketChannel>(){
                   @Override
                   //ChannelInitializer 是一个抽象类，initChannel 方法会在通道创建时被调用。
                   protected void initChannel(SocketChannel channel){
                       // 自定义序列化编解码器
                       // RpcResponse-> ByteBuf 用于将接收到的字节流反序列化为 RpcResponse 对象。
                       channel.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));
                       // ByteBuf -> RpcRequest 用于将 RpcRequest 对象序列化为字节流。
                       channel.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));
                       // 自定义的客户端处理器，用于处理客户端的业务逻辑。
                       channel.pipeline().addLast(new NettyClientHandler());
                   }
               });
    }

    /**
     * 发送消息给服务端
     * @param rpcRequest 消息体
     * @return 服务端返回的数据
     */
    public RpcResponse sendMessage(RpcRequest rpcRequest) {
        try {
            //：使用之前在静态代码块中初始化好的 Bootstrap 对象尝试连接指定主机和端口的服务端，返回一个 ChannelFuture 对象，该对象表示连接操作的异步结果。
            //.sync()：这是一个阻塞方法，会等待连接操作完成。如果连接成功，channelFuture 会包含连接成功的信息；如果连接失败，会抛出异常。
            ChannelFuture channelFuture = bootstrap.connect(host,port).sync();
            logger.info("client connect " + host + ":" + port);
            // 从 ChannelFuture 对象中获取实际的 Channel 对象，该对象代表客户端与服务端之间的连接通道。
            Channel futureChannel = channelFuture.channel();
            logger.info("client write and flush");
            if(futureChannel != null){
                //将 RpcRequest 对象写入通道并刷新，确保数据发送到服务端。
                // 该方法返回一个 ChannelFuture 对象，表示写入和刷新操作的异步结果
                // 为写入和刷新操作的 ChannelFuture 对象添加一个监听器，当操作完成时，会根据操作结果记录相应的日志信息。
                futureChannel.writeAndFlush(rpcRequest).addListener(future -> {
                    if(future.isSuccess()){
                        logger.info("client send message"+rpcRequest.toString());
                    }else{
                        logger.info("client send message failed");
                    }
                });
                // 阻塞等待，直到Channel关闭 这是一个阻塞方法，会等待通道关闭操作完成。通常情况下，客户端会在接收到服务端的响应并处理完后关闭通道。
                futureChannel.closeFuture().sync();
                // 将服务端返回的数据RpcResponse取出
                /**
                 * 作用：
                 * AttributeKey 提供了一种在 Netty 组件间安全存储和共享数据的机制。在 Netty 的异步和多线程环境中，
                 * 使用 AttributeKey 可以方便地在不同的处理器或者操作之间传递和访问数据，同时保证线程安全。
                 * 工作原理：
                 * AttributeKey 是一个泛型类，它的泛型参数指定了存储属性值的类型。
                 * 每个 AttributeKey 都有一个唯一的名称，通过这个名称可以在对应的组件上获取或设置属性值。
                 * 当你使用 AttributeKey 设置属性时，Netty 会将属性值存储在一个线程安全的属性映射中，这样在不同的线程中都可以安全地访问这些属性。
                 */
                AttributeKey<RpcResponse> key = AttributeKey.valueOf("rpcResponse");
                return futureChannel.attr(key).get();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    public static void main(String[] args) {
        RpcRequest rpcRequest = RpcRequest.builder()
                .interfaceName("interface")
                .methodName("hello").build();
        NettyClient nettyClient = new NettyClient("127.0.0.1", 8889);
        for (int i = 0; i < 3; i++) {
            nettyClient.sendMessage(rpcRequest);
        }
        RpcResponse rpcResponse = nettyClient.sendMessage(rpcRequest);
        System.out.println(rpcResponse.toString());
    }
}
